package com.wits.media;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.util.Log;

import com.wits.av.G711Encoder;
import com.wits.av.MFormat;
import com.wits.av.MPacket;
import com.wits.av.MediaSink;
import com.wits.av.MediaType;



/**
 * Created by zhengboyuan on 2018-03-13.
 */

public class AudioEncoder implements MediaEncoder {

    private static final String TAG = "jni PsCamera";

    int audioSource = 0;
    MFormat format;
    MediaSink   sink;

    private AudioRecord audioRecord;
    private int sampleRate;
    private int channels;
    private int frameSize;

    private boolean isRecording = false;
    private Thread recordThread;
    private LinearTimeStamp timeStamp = new LinearTimeStamp();


    G711Encoder encoder;

    public AudioEncoder() {
        encoder = new G711Encoder();
    }

    public void setAudioSource(int source) {
        this.audioSource = source;
    }


    @Override
    public boolean open(MFormat fmt) {
        Log.i(TAG, "AudioEncoder.open");

        format = fmt;

        sampleRate = 44100;
        channels = 1;

        int channelConfig = (channels == 2) ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
        int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
        audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
        if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
            audioRecord = null;
            return false;
        }

        frameSize = bufferSizeInBytes;

        boolean ret = openCodec();

        Log.i(TAG, "AudioEncoder.open end " + ret);

        return ret;
    }

    @Override
    public void close() {
        Log.i(TAG, "AudioEncoder.close");
        closeCodec();
        Log.i(TAG, "AudioEncoder.close end");
    }

    @Override
    public boolean isOpen() {
        return (audioRecord != null) && encoder.isOpen();
    }

    @Override
    public boolean start() {
        Log.i(TAG, "AudioEncoder.start");

        timeStamp.reset();

        isRecording = true;

        if (audioRecord != null) {
            audioRecord.startRecording();

            if (recordThread == null) {
                recordThread = new Thread(new AudioRecordThread());
                recordThread.start();
            }
        }

        Log.i(TAG, "AudioEncoder.start end");
        return true;
    }

    @Override
    public void stop() {
        Log.i(TAG, "AudioEncoder.stop");

        isRecording = false;

        if (recordThread != null) {
            recordThread.interrupt();
            try {
                recordThread.join();
            } catch (Throwable t) {
                //
            }
            recordThread = null;
        }

        if (audioRecord != null) {
            audioRecord.stop();
        }

        Log.i(TAG, "AudioEncoder.stop end");
    }

    @Override
    public boolean isStarted() {
        return isRecording;
    }

    @Override
    public MFormat getFormat() {
        return format;
    }

    @Override
    public void setSink(MediaSink sink) {
        this.sink = sink;
    }


    protected void firePacket(MPacket pkt) {
        if (sink != null) {
            sink.onMediaPacket(pkt);
        }
    }

    protected boolean openCodec() {
        return encoder.open(format.audioCodec, sampleRate, channels);
    }

    protected void closeCodec() {
        encoder.close();

        if (audioRecord != null) {
            audioRecord.release();
            audioRecord = null;
        }
    }

    class AudioRecordThread implements Runnable {

        @Override
        public void run() {

            while (isRecording) {
                try {
                    readAndEncode();
                } catch (Throwable t) {
                    ///
                }
            }
        }

        private void readAndEncode() {
            byte[] audioData = new byte[frameSize];
            int readBytes = audioRecord.read(audioData, 0, audioData.length);
            if (readBytes <= 0) {
                return;
            }

            encode(audioData, readBytes);
        }
    }


    private void encode(byte[] audioData, int length) {
        long pts = timeStamp.get();

        int duration = 0;
        if (m_lastTime > 0) {
            duration = (int) (pts - m_lastTime);
        }
        m_lastTime = pts;

        MPacket pkt = new MPacket();
        pkt.type = MediaType.MEDIA_TYPE_AUDIO;
        pkt.duration = duration;

        if (encoder.encode(audioData, length, pts, pkt)) {
            firePacket(pkt);
        }
    }

    private long	m_lastTime = 0;

}
